package org.vic.decorator;

import java.io.*;

/**
 * 展示装饰器模式在IO中的使用
 * Created by VIC.Huang on 2016/8/29.
 */
public class IOTest {
    public static void main(String[] args) throws IOException {
        final String filePath = "C:/test.txt";
        // InputStream相当于被装饰的接口或者抽象类，FileInputStream相当于原始的待装饰对象，FileInputStream无法装饰InputStream
        // 另外FileInputStream是以只读方式打开了一个文件，并打开了一个文件的句柄存放在FileDescriptor对象的handler属性
        // 所以下面有关回退和重新标记等操作，都是在堆中简历缓冲区所造成的假象，并不是真正的文件流在回退或者重新标记
        InputStream inputStream = new FileInputStream(filePath);
        final int len = inputStream.available();
        System.out.println("FileInputStream不支持mark和reset："+inputStream.markSupported());

        System.out.println("------------------------------------------------------");

        /*下面分别展示三种装饰器的作用：BufferedInputStream, DataInputStream, PushbackInputStream*/
        // 1.装饰成BufferedInputStream，它提供了mark，reset的功能
        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
        System.out.println("BufferedInputStream支持mark和reset：" + bufferedInputStream.markSupported());
        bufferedInputStream.mark(0);
        char c = (char)bufferedInputStream.read();
        System.out.println("文件的第一个字符：" + c);
        bufferedInputStream.reset();
        c = (char)bufferedInputStream.read();
        System.out.println("重置后再读一个字符，依然会是第一个字符：" + c);
        bufferedInputStream.reset();

        System.out.println("------------------------------------------------------");

        // 2.装饰成DataInputStream，我们为了又使用DataInputStream，又使用BufferedInputStream的mark，reset功能，所以我们在进行一层包装
        // 注意，这里如果不使用BufferInputStream，而使用原始的InputStream，read方法返回的结果是-1，即已经读取结束
        // 因为BufferedInputStream已经将文本的内容读取完毕，并缓冲到堆上，默认的初始缓冲区大小是8192B
        DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
        dataInputStream.reset();
        System.out.println("DataInputStream现在具有readInt,readChar,readUTF等功能");
        int value = dataInputStream.readInt();      // 读取一个int，包含四个字节
        // 转换成字符一次显示出来，可以看到文件的前四个字符
        String binary = Integer.toBinaryString(value);
        int first = binary.length() % 8;    // 因为转换成二进制后，前面的0会被省略，顾用取余操作获取第一个8比特的值
        System.out.println("使用readInt读取的前四个字符：");
        for(int i=0; i<4; i++) {
            if(i == 0) {
                System.out.println((char)Integer.valueOf(binary.substring(0, first), 2).intValue());
            }else {
                System.out.println((char)Integer.valueOf(binary.substring((i - 1) * 8 + first, i * 8 + first), 2).intValue());
            }
        }

        System.out.println("------------------------------------------------------");

        // 3.PushbackInputStream无法包装BufferedInputStream支持mark，reset，因为它覆盖了reset和mark方法
        // 因为流已经被读取到末尾，所以我们必须重新打开一个文件的句柄，即FileInputStream
        inputStream = new FileInputStream(filePath);
        PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream, len);
        System.out.println("PushbackInputStream装饰以后支持退回操作unread");
        byte[] bytes = new byte[len];
        pushbackInputStream.read(bytes);    // 读完整个流
        System.out.println("unread回退前的内容：" + new String(bytes));
        pushbackInputStream.unread(bytes);  // 回退
        bytes = new byte[len];             // 清空byte数组
        pushbackInputStream.read(bytes);    // 再读
        System.out.println("unread会退后的内容：" + new String(bytes));

        System.out.println("------------------------------------------------------");

        /* 以上有两个一层装饰和一个两层装饰，下面我们先装饰成Reader，再进行其他装饰 */
        // 由于之前被PushbackInputStream将流读取到末尾，我们需要再次重新打开文件句柄
        inputStream = new FileInputStream(filePath);
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
        System.out.println("InputStreamReader有reader的功能，比如转码：" + inputStreamReader.getEncoding());

        System.out.println("------------------------------------------------------");

        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);  //进一步在reader的基础上装饰成BufferedReader
        System.out.println("BufferedReader有readLine等功能：" + bufferedReader.readLine());

        System.out.println("------------------------------------------------------");

        LineNumberReader lineNumberReader = new LineNumberReader(inputStreamReader);  //进一步在reader的基础上装饰成LineNumberReader
        System.out.println("LineNumberReader有设置行号，获取行号等功能（行号从0开始），当前行号：" + lineNumberReader.getLineNumber());

        System.out.println("------------------------------------------------------");

        // 此处由于刚才被readLine方法将流读取到末尾，所以我们再次重新打开文件句柄，并需要将inputStream再次包装成reader
        inputStreamReader = new InputStreamReader(new FileInputStream(filePath));
        PushbackReader pushbackReader = new PushbackReader(inputStreamReader, len); //进一步在reader的基础上装饰成PushbackReader
        System.out.println("PushbackReader是拥有退回操作的reader对象");
        char[] chars = new char[len];
        pushbackReader.read(chars);
        System.out.println("unread回退前的内容：" + new String(chars));
        pushbackReader.unread(chars);
        chars = new char[len];
        pushbackReader.read(chars);
        System.out.println("unread回退后的内容：" + new String(chars));
    }
}
